大家好!我們昨天逐行解構了 Flutter 的預設 App,徹底搞懂了 MaterialApp
、Scaffold
等核心骨架。今天,我們終於要告別那支藍色的計數器,捲起袖子,作為一名建築師,從一片空白的畫布上,親手搭建屬於「省錢拍拍 (SnapSaver)」的第一個畫面。
今天的任務,掌握 Flutter UI 佈局的「三劍客」:
Container
: 萬能容器,用來設定背景、邊距、大小。Column
: 垂直佈局,讓元件由上到下排列。Row
: 水平佈局,讓元件由左到右排列。同時,釐清 Flutter 佈局中最重要的核心觀念:主軸 (Main Axis) 與 交錯軸 (Cross Axis)。
首先要對 lib/main.dart
進行改造。
MyHomePage
和 _MyHomePageState
這兩個 class。lib/main.dart
的內容。// lib/main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
// 關閉右上角的 Debug 標籤
debugShowCheckedModeBanner: false,
title: 'SnapSaver',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
// 這是我們今天主要的工作區域
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('省錢拍拍 (SnapSaver)'),
backgroundColor: Colors.deepPurple.shade200, // 給 AppBar 一點顏色
),
// body 中心放置一個文字,作為起點
body: const Center(
child: Text('Let\'s start building!'),
),
);
}
}
重新運行 App,你會看到一個乾淨、清爽的頁面。這就是我們的起點。
Container
- 什麼都能裝的萬能容器Container
是 Flutter 中常用的 Widget 之一,可以把它想像成是網頁開發中的div
。它本身是透明看不見的,但可以賦予它各種屬性,讓它成為可見的畫布或空間佔位符。
將 HomePage
的 body
修改成:
body: Center(
child: Container(
width: 200,
height: 200,
// padding: 內邊距,Container 內部 child 與邊界的距離
padding: const EdgeInsets.all(16.0),
// margin: 外邊距,Container 與外部其他元件的距離
margin: const EdgeInsets.all(10.0),
// decoration: 用於設定背景色、邊框、圓角等複雜樣式
decoration: BoxDecoration(
color: Colors.amber.shade100, // 背景色
border: Border.all(color: Colors.black, width: 3), // 邊框
borderRadius: BorderRadius.circular(12), // 圓角
),
child: const Text('This is a Container'),
),
),
就會看到如下圖,有背景、邊框、有圓角的黃色方塊。讓我們試著調整 padding
和 margin
的數值,感受它們的差異。
Column
與 Row
- 佈局的靈魂單一的 Container
不足以構成複雜畫面,我們需要 Column
和 Row
來組織多個 Widget。
核心觀念:主軸 (Main Axis) 與 交錯軸 (Cross Axis)
Column
(垂直排列):
Row
(水平排列):
透過 mainAxisAlignment 和 crossAxisAlignment 這兩個屬性來控制子元件在這兩個軸線上的對齊方式。
我們希望的佈局是:頂部一個總覽區塊,中間是主要功能按鈕。
將 HomePage
的 body
替換成以下程式碼:
body: Column(
// Column 的主軸是垂直的,我們希望內容從頂部開始
mainAxisAlignment: MainAxisAlignment.start,
// Column 的交錯軸是水平的,我們希望內容撐滿寬度
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ // children 屬性接收一個 Widget 列表
// 1. 頂部總覽區塊
Container(
padding: const EdgeInsets.all(24.0),
margin: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(10),
),
child: const Column(
// 這個內部的 Column,我們希望文字靠左對齊
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'本月總支出',
style: TextStyle(fontSize: 16, color: Colors.black54),
),
Text(
'NT\$ 12,345',
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
),
],
),
),
// 2. 中間功能按鈕區塊
Container(
margin: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
// Row 的主軸是水平的,我們希望按鈕之間平均分佈空間
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// 第一個按鈕
Column(
children: [
Icon(Icons.camera_alt, size: 40, color: Colors.deepPurple),
Text('掃描發票'),
],
),
// 第二個按鈕
Column(
children: [
Icon(Icons.edit, size: 40, color: Colors.deepPurple),
Text('手動記帳'),
],
),
// 第三個按鈕
Column(
children: [
Icon(Icons.history, size: 40, color: Colors.deepPurple),
Text('查看紀錄'),
],
),
],
),
),
],
),
程式碼解析
Column
,負責將「總覽區塊」和「功能按鈕區塊」由上到下排列。Container
來設定背景色和邊距,內部再用一個 Column
來排列兩行 Text
。Row
來將三個按鈕由左到右排列。mainAxisAlignment.spaceAround
讓它們在水平方向上均勻分佈。Column
,負責將 Icon
和 Text
上下排列。透過一層層的 Widget 嵌套,像堆樂高一樣,直觀地描述出你想要的畫面。
結構: 將這個畫面的佈局視覺化成以下的樹狀結構
Column
└── children:
├── Container (總覽區塊)
│ └── child: Column
│ └── children:
│ ├── Text ('本月總支出')
│ └── Text ('NT$ 12,345')
└── Container (功能按鈕區塊)
└── child: Row
└── children:
├── Column (掃描發票)
│ ├── Icon
│ └── Text
├── Column (手動記帳)
│ ├── Icon
│ └── Text
└── Column (查看紀錄)
├── Icon
└── Text
今天我們告別了範例程式碼,從一張白紙親手搭建了 App 的第一個畫面。透過掌握 Container
的用法,並徹底理解 Column
和 Row
的主軸與交錯軸概念,我們已經為未來打造任何複雜的佈局,打下了最堅實的基礎。